home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / PXDTUT3.ZIP / PXDTUT3.TXT < prev   
Text File  |  1997-06-01  |  20KB  |  547 lines

  1.  
  2.                   |====================================|
  3.                   |                                    |
  4.                   |   TELEMACHOS proudly presents :    |
  5.                   |                                    |
  6.                   |    Part 3 of the PXD trainers  -   |
  7.                   |                                    |
  8.                   |          3D Vector engine          |
  9.                   |          The basics of 3D          |
  10.                   |                                    |
  11.                   |====================================|
  12.  
  13.          ___---__-->   The Peroxide Programming Tips   <--__---___
  14.  
  15. <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
  16.  
  17.  
  18. Intoduction
  19. -----------
  20.  
  21. Hiya... I'm Telemachos of Peroxide - a new danish group (yes... we DO have
  22. groups in Denmark too :)))  )
  23.  
  24. Since my last trainer quite a few people has mailed me and asked me to do
  25. trainers on various 3D subjects.
  26. As a result of this I have decided to dedicate my next two trainers to the
  27. realm of 3D graphic.
  28.  
  29. What we'll be doing in the next two trainers is to build a 3D vector engine
  30. capable of doing most of the effects used in todays demos. Our engine will
  31. have the following features.
  32.  
  33. Tuturial #3 (this one) :
  34.   - Nice 3D object organization - no unnessesary rotations done.
  35.   - Depth sorting
  36.   - Hidden face removal.
  37.   - Solid Fill
  38.   - Glenzing
  39.  
  40.  
  41. Tuturial #4 (next week or so...) :
  42.   - Z-shading
  43.   - Flat shading according to moving lightsource(s)
  44.   - Gouraud Shading according to moving lightsource(s)
  45.   - Texturemapping
  46.   - Environment-mapping
  47.   - Phong Shading  (a fake 'cause )
  48.  
  49.  
  50. So when you are done with these two tuturials you should be able to to some
  51. nice 3D vector objects.
  52. It might seem that tuturial 4 will be a hard one to get through - but believe
  53. me : We're doing all the preparations in this trainer. Once our 3D engine is
  54. set up all the different shading/fill types are surprisingly alike :)
  55.  
  56.  
  57. *************************** ATTENTION ARTISTS!!! *****************************
  58.  
  59. ARE YOU AN ARTIST ?
  60. DO YOU DRAW VGA-BITMAPS ?
  61. CAN YOU DO GFX IN ALL RESOLUTIONS - 320x200x256 AND VARIOUS SVGA-MODES ?
  62. DO YOU WANT TO SEE YOUR WORK IN AMAZING PRODUCTIONS ?
  63. CAN YOU DO GAME-GFX AS WELL AS BACKGROUNDS FOR DEMOS ?
  64.  
  65. IF YOU MEET THE ABOVE TERMS (or some at least :) ), THEN DROP ME (TELEMACHOS)
  66. A MESSAGE OR MAIL THE GROUP AT :  Peroxide@image.dk
  67.  
  68. ******************************************************************************
  69.  
  70.  
  71. If you want to get in contact with me, there are several ways of doing it :
  72.  
  73. 1) Write me via FIDO-net   :      2:235/350.22
  74.  
  75. 2) E-mail me               :      tm@image.dk
  76.  
  77. 3) Snail mail me           :      Kasper Fauerby
  78.                                   Saloparken 226
  79.                                   8300 Odder
  80.                                   Denmark
  81.  
  82. 4) Call me  (Voice ! )     :    +45 86 54 07 60
  83.  
  84.  
  85. Get this serie from the major demo-related FTP-sites - currently :
  86.  
  87.   GARBO ARCHIVES  (forgot the address)  :  /pc/programming/
  88.  
  89.   ftp.teeri.oulu.fi  :   /msdos/programming/docs/
  90.  
  91.  
  92. Or grap it from my homepage :
  93.  
  94.    Telemachos' Codin' Corner   http://www.image.dk/~tm
  95.  
  96.  
  97.  
  98. FIRST THINGS FIRST! - 3D DEFINATIONS AND TRANSLATIONS
  99. ------------------------------------------------------
  100.  
  101. Lets define the 3D space where our object is placed. The origin of the 3D
  102. object space is located at the middle of the screen and the axis is defined
  103. as shown below :
  104.  
  105.                \ |
  106.               -----------------------------> X-axis
  107.                  | \
  108.                  |   \
  109.                  |     \
  110.                  |       \
  111.                  |         \
  112.                  |           \>
  113.                  |             Z-axis (into the screen :)  )
  114.                  |
  115.                  V
  116.                Y-axis
  117.  
  118.  
  119. Ok.. so our object is made from a number of 3D points each defined by (X,Y,Z)
  120. But how do we plot such a 3D point to the screen ?? We must find some way of
  121. translating the 3 coordinated in the 2 coordinates of the screen.
  122. This is done by calculating the intersection between the vector made from the
  123. 3D point and the eye position and the viewplane.
  124. I won't get into the math involved in this operation - if you'r interrested go
  125. grap a book on vector math and read the bit about intersection between a line
  126. and a plane.
  127. I'll just give you some formulas :
  128.  
  129.  X0 := Xofs  +  Round(X*(Zeye/(Zeye-Z)));
  130.  Y0 := Yofs  +  Round(Y*(Zeye/(Zeye-Z)));
  131.  
  132. Set Xofs to 160 and Yofs to 100 to center object space at the center of the
  133. screen.
  134. If you'r unhappy with these formulas (they are a bit slow because of the
  135. Round and all the floating point muls and divs) you can use a approximation
  136. of the formulas :
  137.  
  138.  X0 := Xofs + (x div (z-Zoff)) shl 8;
  139.  Y0 := Yofs + (y div (z-Zoff)) shl 8;
  140.  
  141. This will also work allthough not entirely correct.
  142.  
  143.  
  144. LETS TALK ABOUT DATA-STRUCTURES
  145. --------------------------------
  146.  
  147. Now for the keyword of 3D programming - DATA STRUCTURES !!
  148.  
  149. In 3D it is VERY important that you think about how to structure your data
  150. so that a minimum of calculations has to be done.
  151. First problem is how to store your 3D object.
  152.  
  153. The easiest way is to store it an array of polygons, each polygon consisting
  154. of 4 3D points. But it is also the way ONE SHOULD NEVER!! store 3D objects.
  155. Lets have a look at a simple cube. A cube consists of 6 sides right ? And each
  156. side consist of 4 points. That sums up to 24 points to be rotated using the
  157. above mentioned data structure.
  158. But as we all know there is only 8 corners in a cube - ie. there is only 8
  159. DIFFERENT points in a cube.
  160. So what we do is to store all the different points in one array - and then
  161. introduce another variable - we could call it faces - to store information
  162. about which points appears in which faces.
  163. This way we only rotates ONE THIRD of the points we rotate using the first
  164. methode.
  165. The goal in 3D is never do do unnessesary calculations. Only rotate each point
  166. ONCE, only draw each pixel on the screen ONCE and so on.
  167.  
  168. Now some words about precision.
  169. As we are using a pretty low resolution (320X200) a fairly big amount of
  170. rounding is performed when rotation / projecting a 3D point. If you store your
  171. 3D object in a single variable and rotates the object into the very same
  172. variable the object will in time be distorted from the roundings. So my advice
  173. is to store the original 3D object in ONE variable and then rotate this
  174. BASEOBJECT into a buffer from which you then perform all the calculations on
  175. the rotated 3D object. This way each rotation starts out with a "fresh" 3D
  176. object and the distortion will be minimal.
  177.  
  178.  
  179. For an idea of a 3D data structure check out the sample program supplied with
  180. this text.
  181. NOTE!!
  182. This is only meant as an idea of a data structure. Modify it to match your
  183. needs.
  184.  
  185.  
  186.  
  187. OK - NOW I WANT TO ROTATE THE POINTS
  188. -------------------------------------
  189.  
  190. Having an object defined by 3D coords is no fun if it just stands still on
  191. the screen. So we want to rotate it around the different axes.
  192.  
  193. For this there is 3 formulas - one for each axis. Please note that the
  194. formulas presented here is the standard rotation formulas given by vector math
  195. books. Numerous optimizations has been made to these routines making rotation
  196. a little faster but I won't get into that in this text. For now the important
  197. thing is to make the points rotate correctly :)
  198. Before we throw ourselves into the formulas one more important note has to
  199. be made. One has to bear in mind that the sequence in which the point is
  200. rotated around the different axes DOES matter. To rotate the point 5 degrees
  201. around the X axis and then 5 degrees around the Y axis is NOT the same as
  202. rotating it about the Y axis and then the X axis!! Think about it!
  203. So, it has been decided that one rotates around the axes in the following
  204. order :  X, Y and then Z.
  205.  
  206. Ok... now it's time for the formulas :
  207.  
  208. rotation around Z-axis :  (rotating the point X,Y,Z  A degrees)
  209.     Xrotated = Cos (A)*X - Sin (A)*Y
  210.     Yrotated = Sin (A)*X + Cos (A)*Y
  211.     Zrotated = Z
  212.  
  213. rotation around X-axis :
  214.     Xrotated = X
  215.     Yrotated = Cos (A)*Y - Sin (A)*Z
  216.     Zrotated = Sin (A)*Y + Cos (A)*Z
  217.  
  218. rotation around Y-axis :
  219.     Xrotated = Cos (A)*X - Sin (A)*Z
  220.     Yrotated = Y
  221.     Zrotated = Sin (A)*X + Cos (A)*Z
  222.  
  223. OK... these are the formulas - check out the sample program for an idea of
  224. how to combine them in a pretty fast way.
  225. I just grapped the rotation routine from an Asphyxia tuturial by Denthor.
  226. His routine is a straight implemention of the above mentioned formulas -
  227. in fixed point assembler. Thanx Denthor.
  228.  
  229.  
  230. OK.. lets sum things up.
  231. By now we have learned to
  232.   - rotate a 3D point around all 3 axis
  233.   - Translate a 3D point into 2D screen coordinates
  234.  
  235. This is allmost enough to get you started coding a 3D object engine. With
  236. this information you could easily code a wirefram engine - as a matter of
  237. fact I suggest you do just that! By coding a simple 3D engine you really learn
  238. the next stuff faster :)
  239.  
  240.  
  241. BACK SO SOON ??? - POLYGON DRAWING
  242. -----------------------------------
  243.  
  244. OK.. I guess you have now coded a wireframe engine (not so hard ehh ??) and
  245. you want more. Lets face it - your demo won't win The Party if it's only
  246. running wireframe 3D graphics.
  247. But we're going to change that - now is the time for solid objects!! (WEE!!)
  248. To draw a solid face we need to code a good polygon routine. We'll code it in
  249. a way so that all the nice shadings in the next tuturial will be really easy
  250. to implement - I'll tell you how right now!
  251.  
  252. What is a polygon ?? In demos most polygons are triangles but today we'll draw
  253. four sided polygons because I'm gonna use a simple square box for my sample
  254. code. Don't worry though - I'll tell you how to do triangles too.
  255. So we got our four points and we want to fill the shape with some color.
  256. But how ?? Well... we draw polygons scanline per scanline. Ie. a polygon is a
  257. bunch of horizontal lines.
  258. So the first thing we'll need is a horizontal line drawer :
  259.  
  260.  
  261. Procedure HorLine(Xbegin, Xend,Ypos : integer;color : byte;where : word);
  262. Assembler;
  263. asm
  264.  mov cx,[Xend]
  265.  inc cx
  266.  sub cx,[Xbegin]   {cx = length of line - used for counter }
  267.                    {note, I assume that Xbegin < Xend - the poly routine}
  268.                    {will take care of that...}
  269.  mov ax,[ypos]
  270.  shl ax,8
  271.  mov di,ax
  272.  shr ax,2
  273.  add di,ax
  274.  add di,[Xbegin]   {di = Ypos * 320 + Xbegin - offset for our line}
  275.  mov es,[where]    {where to draw..}
  276.  
  277.  mov al,[color]
  278.  rep stosb         {I draw byte by byte - slower than drawing a word at a}
  279.                    {time but it is because of the changes we are going to}
  280.                    {make to this routine when glenzing/gouraud/texturemapping}
  281. end;
  282.  
  283.  
  284.  
  285. OK.. now we just have to find out what the X-coords for each scanline is.
  286. We define a variable to hold that information for us :
  287.  
  288.      polygon : Array[0..199,1..2] of byte;
  289.  
  290. This variable can hold the endpoints of a horizontal line for each of the 200
  291. y-lines on the VGA screen.
  292.  
  293. Now we want to fill this buffer out with the correct information. For each of
  294. the four sides (three if it were a triangle) we scan along the edge of the
  295. polygon and updates the polygon variable like this :
  296.  
  297. Procedure ScanPolySide(X1,Y1,X2,Y2 : integer);
  298. var
  299.  DeltaX : integer;
  300.  temp : integer;
  301.  Xposfixed,Xpos : integer;
  302.  counter : integer;
  303. begin
  304.   if Y2=Y1 then exit;          {exit if side is a horizontal line }
  305.   if (Y2<Y1) then              {make sure Y1 is top point}
  306.                begin
  307.                  temp := Y1;
  308.                  Y1 := Y2;
  309.                  Y2 := temp;
  310.  
  311.                  temp := X1;
  312.                  X1 := X2;
  313.                  X2 := temp;   {switch the points if Y1 is not top..}
  314.                end;
  315.  
  316.   DeltaX := ((X2-X1) shl 7) div (Y2-Y1); {DeltaX in 9.7 fixed point math}
  317.   Xposfixed := X1 shl 7; {Xpos in 9.7 fixed point math }
  318.     for counter := Y1 to Y2 do
  319.          begin
  320.            Xpos := XposFixed shr 7;
  321.            if (Xpos < polygon[counter,1]) then polygon[counter,1] := Xpos;
  322.            if (Xpos > polygon[counter,2]) then polygon[counter,2] := Xpos;
  323.            Xposfixed := XposFixed + DeltaX;
  324.          end;
  325. end;
  326.  
  327.  
  328. Now... run this procedure on each side of the poly, and the variable 'polygon'
  329. is set up and ready for our friend Mr. Horline.
  330.  
  331.  
  332. So..
  333.  
  334. Procedure Polygon(X1,Y1,X2,Y2,X3,Y3,X4,Y4 : integer;color : byte; where : word);
  335. var
  336.  counter : integer;
  337.  Ymin, Ymax : integer;
  338.  polygon : Array[0..199,1..2] of integer;
  339.  
  340. begin
  341.  Ymin := Y1;
  342.  Ymax := Y1;
  343.  if (Y2 < Ymin) then Ymin := Y2;
  344.  if (Y2 > Ymax) then Ymax := Y2;
  345.  if (Y3 < Ymin) then Ymin := Y3;
  346.  if (Y3 > Ymax) then Ymax := Y3;
  347.  if (Y4 < Ymin) then Ymin := Y4;
  348.  if (Y4 > Ymax) then Ymax := Y4;  {what is Ymin and Ymax in this polygon ?}
  349.  
  350.  for counter := 0 to 199 do
  351.    begin
  352.      polygon[counter,1] := 32000;
  353.      polygon[counter,2] := -32000;
  354.    end;
  355.  
  356.  {we have to initialize our variable 'polygon' to some extreme values}
  357.  
  358.  ScanPolySide(X1,Y1,X2,Y2);
  359.  ScanPolySide(X2,Y2,X3,Y3);
  360.  ScanPolySide(X3,Y3,X4,Y4);
  361.  ScanPolySide(X4,Y4,X1,Y1); {all four sides scanned}
  362.  
  363.  for counter := Ymin to Ymax do
  364.     Horline(polygon[counter,1],polygon[counter,2],counter,color,where);
  365. end;
  366.  
  367. Well... that was pretty easy... Now we can do solid sides in our 3D cube.
  368. Try it out before continuing :)
  369.  
  370.  
  371.  
  372. WHAT - SOMETHING IS WRONG ??  - DEPTH SORTING :
  373. ------------------------------------------------
  374.  
  375. YES - I know. You have some problems getting your cube look right when having
  376. different colors for each face. This is because of the random order we draw
  377. the faces in.
  378. We have to find some way of depth sorting them. We sort the faces by their
  379. center point. The Z component of their center point, that is.
  380. So for each frame we have to calculate the center Z-value for each face in the
  381. object.
  382. To find the center Z value we add the Z values from the 4 points making the
  383. face and divide this value by 4 (if we are talking triangles obviously we'll
  384. divide by 3)
  385. Only that we don't divide at all! If we don't divide by the number of points
  386. we'll NOT end up with the center Z value. We'll end up with a value that is
  387. 4 times to big. But as ALL the calculated Z values will be 4 times to big
  388. they will still sort corectly.
  389.  
  390. We'll store the values in a variable called
  391.  
  392.      centers : Array[1..Num_of_faces]  of integer;
  393.  
  394. But when we start sorting this table we'll mess up which center that belongs
  395. to which face. To solve this problem we'll introduce another variable called
  396.  
  397.     OrderTable  :  Array[1..Num_of_faces] of integer;
  398.  
  399. This table will store the correct sequence for drawing the faces. Ie. it'll
  400. store the face-numbers belonging to a centervalue.
  401.  
  402. To do the actual sorting we'll use a simple bubble-sort. This is a pretty
  403. simple sorting algoritm which will work for us as long we are not talking
  404. sorting hundreds of faces. If you are rendering LOTS of faces I suggest you
  405. use a quicksort routine instead.
  406. The idea is to scan through the centers variable and compare the entries
  407. two and two. If the value at position+1 is higher than the value at position
  408. we switch the two values and updates the ordertable variable.
  409. We then reset the scan and start over. We continue doing this until we reach
  410. the end of 'centers' without having to switch any values.
  411.  
  412.  
  413. Procedure Sort_faces;
  414. {Just a simple bubble-sort - not to fast but what the heck :) }
  415. {Faces with the HIGHEST Z-val is placed first in Order[] }
  416. VAR
  417.   counter : integer;
  418.   position : integer;
  419.   tempval : integer;
  420. BEGIN
  421.   for counter:=1 to Num_of_faces do BEGIN
  422.     OrderTable[counter]:=counter;
  423.   END;
  424.   {we resets the ordertable so that it matches the unsorted 'centers' variable}
  425.   position := 1;
  426.  
  427.   repeat
  428.     if (centers[position] < centers[position+1]) then
  429.         BEGIN
  430.           tempval := Centers[position+1];
  431.           Centers[position+1] := centers[position];
  432.           centers[position] := tempval;
  433.  
  434.           tempval := OrderTable[position+1];
  435.           OrderTable[position+1] := OrderTable[position];
  436.           OrderTable[position] := tempval;
  437.  
  438.           position:=1;
  439.         END;
  440.       inc(position);
  441.   until (position = Num_of_faces);
  442. END;
  443.  
  444.  
  445. Now when doing the faces we draw them in the order specified in OrderTable.
  446. Go ahead... try it out...
  447.  
  448.  
  449.  
  450. HIDDEN FACE REMOVAL
  451. --------------------
  452.  
  453. Fire up your 3D engine and take a look at the rotating cube.
  454. Looks cool ehh ?? Nice work... YOU did that ;)
  455. But try and count the faces visible at a time. How many faces do you see at
  456. one time ? Only about half of them ????
  457. ARGH!!  We are drawing half an object that is never seen ???
  458. This won't do. Right now it does not matter much as drawing a polygon is fairly
  459. fast - but next week when we start texturemapping or shading the faces it will
  460. slow things down.
  461. So - we'll just have to remove the faces we do not see. This is fortunately
  462. VERY!! easy.
  463. To be able to perform this operation it is important that you have defined
  464. your faces in a clockwise direction. Ie. all faces should be defined as :
  465.  
  466.  
  467.                    P1               P2
  468.                     |---------------|
  469.                     |               |
  470.                     |               |
  471.                     |               |
  472.                     |               |
  473.                     |               |
  474.                     |---------------|
  475.                    P4               P3
  476.  
  477.  
  478. Ok... now I'll introduce a new formula to you - the cross product. The cross
  479. product is a formula that returns the normal to a plane :
  480.  
  481.         Xnormal=(P2.Y-P1.Y)(P1.Z-P3.Z)-(P2.Z-P1.Z)(P1.Y-P3.Y)
  482.         Ynormal=(P2.Z-P1.Z)(P1.X-P3.X)-(P2.X-P1.X)(P1.Z-P3.Z)
  483.  
  484.         Znormal=(P2.X-P1.X)(P1.Y-P3.Y)-(P2.Y-P1.Y)(P1.X-P3.X)
  485.  
  486. Well... for now we are only interrested in the Z normal. If the Z normal is
  487. negative it means that the normal points towards us - ie. we draw the face.
  488. If not we discard it.
  489.  
  490. One more important note to make is that because the Znormal does not involve
  491. any Z components of the points we can use it on the PROJECTED 2D values.
  492.  
  493. Now go implement this in your 3D engine - if you are having trouble take a
  494. look at the sample program.
  495. Next week we'll see how we use this normal (the hole 3D normal though) for
  496. shading...
  497.  
  498.  
  499.  
  500. ONE LAST THING - GLENZING THE POLYGON
  501. --------------------------------------
  502.  
  503. OK.. now we have done a 3D object engine that handles 3D objects correct -
  504. showing only what HAS to be shown.
  505. That done I think you are ready for another kind of fill - that TOTALLY
  506. ignores ALL we have learned about sorting and hidden face removal... SIGH,
  507. thats life :)
  508.  
  509. The effect is called glenzing and the idea is to make the object look like it
  510. is made from colored glass. This effect is done by setting the correct
  511. pallette. I don't think there is any way of calculating this pallette so you'll
  512. have to experiment.
  513. Now when drawing your faces you do not draw the polygon in one color. For each
  514. pixel you grap the background color and add it to the face color.
  515. This is of course a little slower than doing single colored polygons but not
  516. much. You'll have to change your horizontal line drawer to do this - check
  517. the sample program if you experiment any trouble.
  518.  
  519. When glenzing you should obviously NOT remove the faces pointing away from
  520. you as they can be seen through the glass sides.
  521. Sorting is useless.... the result will look the same with or without sorting.
  522. In my opinion glenzing is not a cool effect - but well... it can't hurt to
  523. know how to do it :)
  524.  
  525.  
  526.  
  527. LAST REMARKS
  528. -------------
  529.  
  530. Well, that's about all for now.
  531. Hope you found this doc useful - and BTW : If you DO make anything public using
  532. these techniques please mention me in your greets or where ever you se fit.
  533. I DO love to see my name in a greeting :=)
  534.  
  535.  
  536. As mentioned earlier my next tuturial will be on different types of shading -
  537. using this 3D object engine as a base...
  538. But after that ??
  539. If you have any good ideas for a subject you wish to see a tuturial on please
  540. mail me. If I like the idea (and know anything about it :)  ) I'll write a
  541. tut on it.
  542.  
  543. Keep on coding...
  544.  
  545. Telemachos - May '97.
  546.  
  547.